#include "CorrelationBasedCF.h"

CorrelationBasedCF::CorrelationBasedCF() {
	active_uid = NOT_FOUND;
	daoFactory = DAOFactory::getDAOFactory();
}

double CorrelationBasedCF::predict(int cid) {
	if (populateRMatrix() == 0)
		return 0;

	avr_a_user = ratingsAverage(active_uid, cid);
	if (active_uid == NOT_FOUND) {
		Log::verbose("Guest user - no ratings to calculate (yet)");
		return 0;
	}

	return calculatePrediction(cid);
}

vector<string> CorrelationBasedCF::listTopN(int n) {
	if (populateRMatrix() == 0)
		return vector<string> ();

	multimap<double, int> neighborhood;

	for (unsigned i = 0; i < users.size(); i++)
		if (users[i] != active_uid)
			neighborhood.insert(pair<double, int> (
					pearsonCorrelation(users[i]), users[i]));

	multimap<int, int> topnlist;
	multimap<double, int>::reverse_iterator riter;
	for (unsigned int i = 0; i < itens.size(); i++) {
		if (ratings[pair<int, int> (active_uid, itens[i])] == NOT_RATED) {
			int sum = 0;
			for (riter = neighborhood.rbegin(); riter != neighborhood.rend(); riter++)
				if (ratings[pair<int, int> (riter->second, itens[i])]
						!= NOT_RATED)
					sum += ratings[pair<int, int> (riter->second, itens[i])];
			topnlist.insert(pair<int, int> (sum, itens[i]));
		}
		if (topnlist.size() == (unsigned) n)
			break;
	}

	int i;
	vector<string> rec_itens;
	rec_itens.push_back(active_user_mac);
	multimap<int, int>::reverse_iterator iter;
	for (iter = topnlist.rbegin(), i = 1; iter != topnlist.rend(); iter++, i++) {
		rec_itens.push_back(daoFactory->getContentDAO()->loadContentById(
				iter->second).getName());
		Log::log_oss << "Recommendation " << i << ": " << rec_itens.back();
		Log::verbose();
	}

	return rec_itens;
}

int CorrelationBasedCF::populateRMatrix() {
	list<Preference> preferences;
	try {
		daoFactory->connect_db();
		preferences = daoFactory->getPreferenceDAO()->loadAllPreferences();
	} catch (std::exception &ex) {
		Log::error(ex.what());
		return 0;
	}

	if (preferences.size() == 0) {
		Log::verbose("No ratings found");
		return 0;
	}

	list<Preference>::const_iterator iter = preferences.begin();
	int cid, uid;

	while (iter != preferences.end()) {
		uid = iter->getUser().getId();
		cid = iter->getContent().getId();
		if (active_uid == NOT_FOUND and iter->getUser().getDeviceCode()
				== active_user_mac)
			active_uid = uid;
		if (find(users.begin(), users.end(), uid) == users.end())
			users.push_back(uid);
		if (find(itens.begin(), itens.end(), cid) == itens.end())
			itens.push_back(cid);

		ratings.insert(pair<pair<int, int> , int> (pair<int, int> (uid, cid),
				iter->getRating()));

		iter++;
	}

	return preferences.size();
}

double CorrelationBasedCF::ratingsAverage(int& uid, int& cid) {
	double average = 0;
	int quotient = 0;

	for (matrix::const_iterator iter = ratings.begin(); iter != ratings.end(); iter++)
		if (uid == iter->first.first and cid != iter->first.second
				and iter->second != NOT_RATED) {
			average += iter->second;
			quotient++;
		}

	return average / quotient;
}

double CorrelationBasedCF::pearsonRatingsAverage(int& uid) {
	int quotient = 0;
	int sum1 = 0, sum2 = 0;
	int term1, term2;

	p_avr_a_user = 0;

	for (unsigned int i = 0; i < itens.size(); i++) {
		term1 = ratings[pair<int, int> (active_uid, itens[i])];
		if (term1 == 0)
			continue;
		sum1 += term1;

		term2 = ratings[pair<int, int> (uid, itens[i])];
		if (term2 == 0) {
			sum1 -= term1;
			continue;
		}
		sum2 += term2;

		quotient++;
	}

	if (quotient == 0) {
		p_avr_a_user = 0;
		return 0;
	}

	p_avr_a_user = (double) sum1 / quotient;
	return (double) sum2 / quotient;
}

double CorrelationBasedCF::pearsonCorrelation(int& uid) {
	double avr_uid = pearsonRatingsAverage(uid);
	int rating_a_uid, rating_uid;
	double factor1, factor2;
	double num_sum = 0, den_sum1 = 0, den_sum2 = 0;

	for (unsigned int i = 0; i < itens.size(); i++) {
		rating_a_uid = ratings[pair<int, int> (active_uid, itens[i])];
		if (rating_a_uid == 0)
			continue;
		rating_uid = ratings[pair<int, int> (uid, itens[i])];
		if (rating_uid == 0)
			continue;

		factor1 = rating_a_uid - p_avr_a_user;
		factor2 = rating_uid - avr_uid;

		num_sum += factor1 * factor2;
		den_sum1 += pow(factor1, 2);
		den_sum2 += pow(factor2, 2);
	}

	if (num_sum == 0)
		return 0;

	return num_sum / ((sqrt(den_sum1) * sqrt(den_sum2)));
}

double CorrelationBasedCF::calculatePrediction(int& cid) {
	map<int, double> avr_users, pearson_users;
	double num_sum = 0, den_sum = 0;
	int uid;

	for (matrix::const_iterator iter = ratings.begin(); iter != ratings.end(); iter++) {
		if (iter->second == NOT_RATED)
			continue;

		uid = iter->first.first;
		if (uid != active_uid) {
			if (avr_users.find(uid) == avr_users.end()) {
				avr_users[uid] = ratingsAverage(uid, cid);
				pearson_users[uid] = pearsonCorrelation(uid);
				den_sum += abs(pearson_users[uid]);
			}
			if (cid == iter->first.second)
				num_sum += (iter->second - avr_users[uid]) * pearson_users[uid];
		}
	}

	return avr_a_user + num_sum / den_sum;
}

CorrelationBasedCF::~CorrelationBasedCF() {
	daoFactory->disconnect_db();
}
